#include "graphics.h"

#include <stdio.h>
#include <math.h>

#define PI 3.1415926535897932384626

const int sc_width  = 640;
const int sc_height = 640;
typedef void * HANDLE;

typedef struct STAR
{
    float x;
    float y;
    float z;
    float r;
    float R;
    float rad;
    int color;
} *PSTAR;

typedef struct SCENE
{
    float   dx;
    float   dz;
    float   drz;
    float   fs;
    int     key[4];
    PSTAR   star;
    int     nMaxStar;
} *PSCENE;

double frandom()
{
    return (double)random(10000) / 10000;
}

void InitStar(PSTAR star)
{
    star->y     = (float)(frandom() * 2 - 1.0);
    star->R     = (float)sqrt(1 - star->y * star->y);
    star->r     = (float)(frandom() * PI * 2);
    star->rad   = (float)(frandom() * 0.01 + 0.01);
    star->color = 0xFFFFFF;
}

void MoveStar(PSTAR star, double dt)
{
    star->r += star->rad * (float)dt;
    if (star->r > 2*PI)
    {
        star->r -= (float)(2*PI);
    }
    star->x = (float)(star->R * cos(star->r));
    star->z = (float)(star->R * sin(star->r));
}

int alpha(int color, float a)
{
    int r = color&0xff, g = (color>>8)&0xff, b = (color>>16)&0xff;
    r = (int)(r*a);
    g = (int)(g*a);
    b = (int)(b*a);
    return rgb(r, g, b);
}

void DrawStar(PSTAR star, float dz, float drz)
{
    float bz = 0.5f;
    int color = star->color;
    {
        struct point3d t = {star->x, star->y, star->z};
        rotate_point3d_z(&t, drz);
        {
            float z = star->z + dz;
            if (z < 1.0f + bz)
            {
            }
            else if (z > 0)
            {
                color = alpha(color, 1.0f / (z - bz));
            }
            draw_point(&t, color);
        }
    }
}

int __stdcall on_msg_key(HANDLE param, unsigned msg, int key)
{
    PSCENE scene = (PSCENE) param;
    if (msg == MSG_EVENT_DOWN)
    {
        if (key == 'W')
        {
            scene->key[0] = 1;
        }
        else if (key == 'S')
        {
            scene->key[1] = 1;
        }
        else if (key == 'A')
        {
            scene->key[2] = 1;
        }
        else if (key == 'D')
        {
            scene->key[3] = 1;
        }
    }
    else if (msg == MSG_EVENT_UP)
    {
        if (key == 'W')
        {
            scene->key[0] = 0;
        }
        else if (key == 'S')
        {
            scene->key[1] = 0;
        }
        else if (key == 'A')
        {
            scene->key[2] = 0;
        }
        else if (key == 'D')
        {
            scene->key[3] = 0;
        }
    }
    return 0;
}

int __stdcall on_update(HANDLE param, float ms)
{
    PSCENE scene = (PSCENE)param;
    scene->fs += 0.003f;
    if (scene->fs >= 1.0f)
    {
        scene->fs -= 1.0f;
    }
    scene->drz += 0.01f;
    if (scene->drz > 2*PI)
    {
        scene->drz -= (float)(2*PI);
    }
    if (scene->key[0])
    {
        scene->dz -= 0.01f;
    }
    if (scene->key[1])
    {
        scene->dz += 0.01f;
    }
    if (scene->key[2])
    {
        scene->dx -= 0.01f;
    }
    if (scene->key[3])
    {
        scene->dx += 0.01f;
    }
    return 0;
}

int __stdcall on_render(HANDLE param, float ms)
{
    double dt = ms * 0.06;
    PSCENE scene = (PSCENE) param;
    int i;

    cleardevice();
    {
        struct point3d t = {scene->dx, 0, -scene->dz};
        set_viewpoint(&t);
    }
    for (i = 0; i < scene->nMaxStar; i++)
    {
        MoveStar(scene->star + i, dt);
        DrawStar(scene->star + i, scene->dz, scene->drz);
    }
    {
        struct point3d pcenter[2] = 
        {
            {0, 1, 0},
            {0,-1, 0}
        };
        struct point3d pbox[8] = 
        {
            { 1, 1, 1},
            { 1, 1,-1},
            { 1,-1, 1},
            { 1,-1,-1},
            {-1, 1, 1},
            {-1, 1,-1},
            {-1,-1, 1},
            {-1,-1,-1},
        };
        rotate_point3d_z(&pcenter[0], scene->drz);
        rotate_point3d_z(&pcenter[1], scene->drz);

        setcolor(0xFF202020);
        draw_line(&pbox[0], &pbox[1]);
        draw_line(&pbox[2], &pbox[3]);
        draw_line(&pbox[4], &pbox[5]);
        draw_line(&pbox[6], &pbox[7]);
        draw_line(&pbox[0], &pbox[3]);
        draw_line(&pbox[1], &pbox[2]);
        draw_line(&pbox[4], &pbox[7]);
        draw_line(&pbox[5], &pbox[6]);
        draw_line(&pbox[0], &pbox[5]);
        draw_line(&pbox[1], &pbox[4]);
        draw_line(&pbox[2], &pbox[7]);
        draw_line(&pbox[3], &pbox[6]);

        draw_line(&pcenter[0], &pcenter[1]);

        setcolor(0x202020);
        draw_line_f(&pbox[0], &pbox[2]);
        draw_line_f(&pbox[1], &pbox[3]);
        draw_line_f(&pbox[4], &pbox[6]);
        draw_line_f(&pbox[5], &pbox[7]);
        draw_line_f(&pbox[0], &pbox[4]);
        draw_line_f(&pbox[1], &pbox[5]);
        draw_line_f(&pbox[2], &pbox[6]);
        draw_line_f(&pbox[3], &pbox[7]);
    }
    setcolor(HSVtoRGB(scene->fs, 1.0f, 1.0f));
    outtextxy(0, 0, (char*)"Press W, S, A or D to move the camera");
    return 0;
}

int main()
{
    #define MAXSTAR 500
    struct STAR star[MAXSTAR];
    struct SCENE scene = {0};
    int i;
    {
        int g = TRUECOLORSIZE, m = SIZE2DWORD(sc_width, sc_height);
        initgraph(&g, &m, "3d Ball");
    }

    scene.star      = star;
    scene.drz       = 0;
    scene.dx        = 0;
    scene.dz        = 3;
    scene.fs        = 0;
    scene.nMaxStar  = MAXSTAR;

    set_3dview((float)(sc_width / 2), (float)(sc_height / 2), (float)(sc_width), (float)(sc_height));
    for (i = 0; i < scene.nMaxStar; i++)
    {
        InitStar(star + i);
    }
    message_addkeyhandler(&scene, on_msg_key);
    ege_gameloop(on_update, &scene, 60, on_render, &scene, 0);

    closegraph();
    #undef MAXSTAR
    return 0;
}
